'''
Created on Oct 29, 2009

@author: Owner
'''
import sqlite3
import array
import os.path
import random

FILE_DB = "anch_tournament.db3"
DB_FILE_PATH = None
_training_mode = False
proccessing_queue = []
_debug = False

def getDatabaseFile():
	global DB_FILE_PATH
	if DB_FILE_PATH == None:
		DB_FILE_PATH = os.path.join(\
			os.path.dirname(__file__), FILE_DB)
	return DB_FILE_PATH
	

class AnchTournamentPlayer(object):
	'''
	classdocs
	'''
	__hit_counter = 0
	
	training_mode = False
	SQL = "INSERT OR REPLACE INTO strategy VALUES (?, ?, ?, ?, ?, ?, ?)"
	
	def __init__(self, state, player):
		'''
		Constructor
		'''
		self.state = state
		self.myself = player
		self.opponent = player % 2 + 1
		self.state_str = None
		
	def tournamentMove(self):
		adaRecord = self.fetchDbInfoByState()
		if _training_mode:
			move = adaRecord.getMoveByProb()
			proccessing_queue.append((adaRecord, move))
			return move
		
		if 	adaRecord.type == 'N':
			#Not training mode? Then resort to other approach instead
			return None
		else:
			return adaRecord.getMoveByProb()
			
			
	def getEncodedState(self):
		if self.state_str != None:
			return self.state_str
		
		self.state_str = AnchTournamentPlayer.encodeState(self.state, self.myself)
		return self.state_str
	
	def fetchDbInfoByState(self):
		conn = sqlite3.connect(getDatabaseFile())
		conn.text_factory = str
		c = conn.cursor()
		str_state = self.getEncodedState()
		c.execute("select * from strategy where state=?", (sqlite3.Binary(str_state),))
		
		row = c.fetchone()
		
		if row == None:
			return AnchDatabaseAgent.generateNewEntity(self.state, self.myself)
		else:
			AnchTournamentPlayer.__hit_counter+=1
			return AnchDatabaseAgent.generateExistedEntity(self.state, self.myself, row)

		conn.close()
			
	@staticmethod
	def encodeState(state, current_player):
		bytes = array.array('B', [0] * 9)
		
		pits = AnchTournamentPlayer.getStatePatternArray(state, current_player)
		
		#Every pits not exceed 48 < 2^6

		p = 0
		for i in range(3):
			j = i * 3
			bytes[j] = pits[p] << 2
			p+=1
			bytes[j] = bytes[j] ^ (pits[p] >> 4)
			j+=1
			bytes[j] =  (pits[p] & 0xF) << 4
			p+=1
			bytes[j] = (bytes[j] ^ (pits[p] >> 2) ) & 0xff
			j+=1
			bytes[j] = ((pits[p] << 6) ^ pits[p+1]) & 0xff
			p+=2
		
		return bytes.tostring()
	
	@staticmethod
	def getStatePatternArray(state, current_player):
		pits = [0] * 12
		opponent = current_player % 2 + 1
		for i in range(6):
			#arrange our side first, so we can form a more reusable pattern			
			pits[i] = state.get_pit_count(current_player, i)
			pits[i + 6] = state.get_pit_count(opponent, i)
			
		return pits
	
	@staticmethod
	def updateDatabase(win):
		global proccessing_queue
		if win:
			fn = AnchDatabaseAgent.increaseProbByMove
		else:
			fn = AnchDatabaseAgent.decreaseProbByMove
		
		try:	
			for elem in proccessing_queue:
				fn(elem[0], elem[1])
		except:
			pass
		
		conn = sqlite3.connect(getDatabaseFile())
		conn.text_factory = str
		c = conn.cursor()
		
		for elem in proccessing_queue:
			ada = elem[0]
			c.execute(AnchTournamentPlayer.SQL, (sqlite3.Binary(ada.state_str),\
				ada.p[0], ada.p[1],	ada.p[2], \
				ada.p[3], ada.p[4],	ada.p[5]))
		
		conn.commit()
		conn.close()
	
	@staticmethod
	def resetCounter():
		AnchTournamentPlayer.__hit_counter = 0
	
	@staticmethod
	def getCounter():
		return AnchTournamentPlayer.__hit_counter
	
ATP = AnchTournamentPlayer

class AnchDatabaseAgent(object):
	def __init__(self, state, type, currentPlayer):
		'''
		Constructor
		'''
		self.type = type # 'U' for update, 'N' for new
		self.state = state
		self.current_player = currentPlayer
		self.state_str = AnchTournamentPlayer.encodeState(state, currentPlayer)
		self.p = [0] * 6
	
	def isLegal(self):
		pc = []
		pc.extend(self.p)
		
		ori_exp = self.state.controller.expansions
		for move in self.state.successor_moves():
			pc[move.get_move()] = 0
		
		legal = True
		for i in pc:
			if i != 0:
				legal = False			
		self.state.controller.expansions = ori_exp
		
		return legal
		
	
	@staticmethod
	def generateNewEntity(state, currentPlayer):
		ada = AnchDatabaseAgent(state, 'N', currentPlayer)
		ori_exp = state.controller.expansions
		
		successors = state.successor_moves()
		count_moves = len(successors)
		prob = 1.0 / count_moves
		for successor in successors:
			ada.p[successor.get_move()] = prob
			
		state.controller.expansions = ori_exp
		return ada
	
	@staticmethod
	def generateExistedEntity(state, currentPlayer, row):
		ada = AnchDatabaseAgent(state, 'U', currentPlayer)
		ada.state_str = row[0]
		for i in range(6):
			ada.p[i] = row[i+1]
		legal = ada.isLegal()	
		return ada if legal else \
			AnchDatabaseAgent.generateNewEntity(state, currentPlayer) # bad db result	
	
	def getMoveByProb(self):
		global _debug
		random.seed()
		rand = random.random()
		successors = self.state.successor_moves()
		accum = 0
		for successor in successors:
			accum += self.p[successor.get_move()]
			if accum >= rand:
				if _debug:
					print ("[{1}|[{2}]]\nChose Move:{0}".format(successor, rand, self.p))
				return successor
		
		pass
	
	def normalizeProbs(self):
		sum = 0
		for i in range(6):
			sum += self.p[i]
			
		for i in range(6):
			self.p[i] = float(self.p[i]) / sum
				
		pass
	
	@staticmethod
	def increaseProbByMove(ada, move):
		move_pit = move.get_move()
		ada.p[move_pit] *= 1.1
		ada.normalizeProbs()
		pass
	
	@staticmethod
	def decreaseProbByMove(ada, move):
		move_pit = move.get_move()
		ada.p[move_pit] *= 0.9
		ada.normalizeProbs()
		pass